﻿using System;
using System.Collections.Generic;
using System.Globalization;
using QAToolsAddin.Components;
using QAToolsAddin.GenericDialogs;
using QAToolsAddin.GenericDialogs.Fields;
using Excel = Microsoft.Office.Interop.Excel;

namespace QAToolsAddin.UI {
    class Gen {
        private readonly Excel.Application _excelapp;
        private readonly Excel.Range _range;
        private CultureInfo _culture;
        private bool _singlesel;
        private FieldNumber _fnb;

        public Gen(Excel.Application application, CultureInfo culture) {
            try {
                _excelapp = application;
                _culture = culture;
                _range = application.ActiveWindow.RangeSelection;
                _singlesel = _range.Columns.Count == 1 && _range.Rows.Count == 1;
                _fnb = _singlesel ? new FieldNumber("Number of rows", "", 100, 1, true) : null;
            } catch (Exception ex) { new FrmException(_excelapp, ex).ShowDialog(); }
        }

        private class Generator {
            public delegate object Next_Delegate();

            private Dictionary<object, bool> _generated;
            private bool _unique;
            private Next_Delegate _nextDelegate;

            public Generator(bool unique, Next_Delegate nextDelegate) {
                _generated = new Dictionary<object, bool>();
                _unique = unique;
                _nextDelegate = nextDelegate;
            }

            public object Next() {
                object value = null;
                int loop = 0;
                do {
                    value = _nextDelegate();
                    if (loop++ > 10000)
                        throw new InputError("Faild to generate unique values. Try with higher bounds.");
                } while (_unique && _generated.ContainsKey(value));
                if (_unique)
                    _generated.Add(value, false);
                return value;
            }
        }

        public void Date() {
            var f1 = new FieldDate("Min date", "Minimum date", System.DateTime.Now.Date, true);
            var f2 = new FieldDate("Max date", "Maximum date", System.DateTime.Now.AddYears(1).Date, true);
            var f3 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Random time generation", f1, f2, f3, _fnb).Show(delegate {
                var minDate = f1.Value;
                var maxDate = f2.Value;
                int maxDays = (int)(maxDate - minDate).TotalDays + 1;
                var unique = f3.Value;
                string format = "m/d/yyyy";
                var random = new Random();
                var gen = new Generator(f3.Value, () => minDate.AddDays(random.Next(maxDays)));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void Time() {
            var f1 = new FieldTime("Min time", "Minimum time", new System.DateTime(1899, 12, 30), true);
            var f2 = new FieldTime("Max time", "Maximum time", new System.DateTime(1899, 12, 30).AddSeconds(86399), true);
            var f3 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Random time generation", f1, f2, f3, _fnb).Show(delegate {
                System.DateTime minTime = f1.Value, maxTime = f2.Value;
                var maxSeconds = (int)(maxTime - minTime).TotalSeconds + 1;
                var random = new Random();
                string format = "[$-F400]h:mm:ss AM/PM";
                var gen = new Generator(f3.Value, () => minTime.AddSeconds(random.Next(maxSeconds)));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void DateTime() {
            var f1 = new FieldDate("Min date", "Minimum date", System.DateTime.Now.Date, true);
            var f2 = new FieldDate("Max date", "Maximum date", System.DateTime.Now.AddMonths(1).Date, true);
            var f3 = new FieldTime("Min time", "Minimum time", new System.DateTime(1899, 12, 30), true);
            var f4 = new FieldTime("Max time", "Maximum time", new System.DateTime(1899, 12, 30).AddSeconds(86399), true);
            var f5 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Random date-time generation", f1, f2, f3, f4, f5, _fnb).Show(delegate {
                System.DateTime minDate = f1.Value, maxDate = f2.Value;
                System.DateTime minTime = f3.Value, maxTime = f4.Value;
                var maxDays = (int)(maxDate - minDate).TotalDays + 1;
                var maxSeconds = (int)(maxTime - minTime).TotalSeconds + 1;
                var random = new Random();
                string format = _culture.DateTimeFormat.ShortDatePattern + " HH:mm";
                var gen = new Generator(f5.Value, () => minDate.AddDays(random.Next(maxDays)).AddSeconds(random.Next(maxSeconds)));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void Number() {
            var f1 = new FieldNumber("Min number", "Minimum number", 0d, true);
            var f2 = new FieldNumber("Max number", "Maximum number", 1000d, true);
            var f3 = new FieldNumber("Precision", "Precision for floating numbers", 0d, 0d, 15, true);
            var f4 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Random number generation", f1, f2, f3, f4, _fnb).Show(delegate {
                var min = f1.Value ?? 0;
                var gap = f2.Value ?? double.MaxValue - min;
                var precision = (int)(f3.Value ?? 0);
                var unique = f4.Value;
                var random = new Random();
                string format = null;
                var gen = new Generator(f4.Value, () => Math.Round(random.NextDouble() * gap + min, precision));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void Coordinates() {
            var f1 = new FieldNumber("Latitude min", "Minimum latitude", -90d, -90d, 90d, true);
            var f2 = new FieldNumber("Latitude max", "Maximum latitude", 90d, -90d, 90d, true);
            var f3 = new FieldNumber("Longitude min", "Maximum longitude", -180d, -180d, 180d, true);
            var f4 = new FieldNumber("Longitude max", "Minimum longitude", 180d, -180d, 180d, true);
            var f5 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Generate GPS coordinates", f1, f2, f3, f4, f5, _fnb).Show(delegate {
                var precision = 6;
                double minLat = f1.Value ?? 0;
                double gapLat = (f2.Value ?? 0) - minLat;
                double minLon = f3.Value ?? 0;
                double gapLon = (f4.Value ?? 0) - minLon;
                var unique = f5.Value;
                var random = new Random();
                string format = "@";
                var genLat = new Generator(unique, () => Math.Round(random.NextDouble() * gapLat + minLat, precision));
                var genLon = new Generator(unique, () => Math.Round(random.NextDouble() * gapLon + minLon, precision));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = "'" + genLat.Next().ToString() + " " + genLon.Next().ToString(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = "'" + genLat.Next().ToString() + " " + genLon.Next().ToString(); });
            });
        }

        public void Amount() {
            var f1 = new FieldNumber("Min amount", "Minimum amount", 0d, true);
            var f2 = new FieldNumber("Max amount", "Maximum amount", 100d, true);
            var f3 = new FieldCheckBox("Generate unique values", "", false);
            new InputBox(_excelapp, "Random amount generation", f1, f2, f3, _fnb).Show(delegate {
                var min = f1.Value ?? 0;
                var gap = (f2.Value ?? 0) - min;
                var random = new Random();
                string format = Utils.GetCurrencyFormat(_culture.LCID, _culture.NumberFormat.CurrencySymbol, _culture.NumberFormat.CurrencyPositivePattern);
                var gen = new Generator(f3.Value, () => Math.Round(random.NextDouble() * gap + min, 2));
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void String() {
            var f1 = new FieldText("Pattern", @"\d=0-9  \w=A-Za-z0-9_  \H=0-9A-F", @"[A-F]-\d{3}-\d{3,10}", true);
            var f2 = new FieldCheckBox("Generate unique values", "", false, true);
            new InputBox(_excelapp, "Random string generation", f1, f2, _fnb).Show(delegate {
                if (f1.Value.Length < 1) 
                    throw new InputError("Pattern field is empty");
                var gen = new RandomString(f1.Value, f2.Value);
                string format = "@";                
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, (ref object value) => { value = gen.Next(); });
                else
                    new CellsOperator(_range, true).ForEachValue(format, (ref object value) => { value = gen.Next(); });
            });
        }

        public void Lorem() {
            var f1 = new FieldNumber("Length", "Number of words", 10d, 0d, 5000d, true);
            new InputBox(_excelapp, "Random words generation", f1, _fnb).Show(delegate {
                var lorem = new RandomWords((int)(f1.Value ?? 0));
                string format = "@";
                CellsOperator.CellWriter writer = (ref object value) => { value = lorem.Next(); };
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, writer);
                else
                    new CellsOperator(_range, true).ForEachValue(format, writer);
            });
        }

        public void List() {
            var f1 = new FieldText("List of values", "List of values seperated by a semicolon", "value1;value2", true);
            new InputBox(_excelapp, "Random generation from a list", f1, _fnb).Show(delegate {
                var values = f1.Value.Split(';');
                var max = values.Length;
                var random = new Random();
                string format = null;
                CellsOperator.CellWriter writer = (ref object value) => { value = values[random.Next(max)]; };
                if (_singlesel)
                    CellsOperator.AddDataToRange(_range, (int)_fnb.Value, format, writer);
                else
                    new CellsOperator(_range, true).ForEachValue(format, writer);
            });
        }

        public void Combination() {
            var f1 = new FieldText("Set of values", "List of values seperated by a semicolon", "a;b;c", "^.+;.+$", "A minimum of 2 values is required", true);
            var f2 = new FieldNumber("Length (k)", "Length (k)", 2, 1, true);
            var f3 = new FieldCheckBox("With permutation : AB => AB,BA...", "", false, true);
            var f4 = new FieldCheckBox("With repetition : AB => AA,BB,AB...", "", false, true);
            var f5 = new FieldCheckBox("With sub length : AB => A,B,AB...", "", false, true);
            new InputBox(_excelapp, "Generate combination", f1, f2, f3, f4, f5).Show(delegate {
                var values = f1.Value.Split(';');
                var n = values.Length;
                var k = (int)f2.Value;
                var order = f3.Value;
                var repeat = f4.Value;
                var subset = f5.Value;
                if (k > n)
                    throw new InputError("Length (k) has to be superior or equal to the number of values");
                var data = Combinaison.Get(values, k, order, repeat, subset);
                CellsOperator.AddDataToRange(_range, data);
            });
        }

        public void ExhaustiveCombinations() {
            var fields = new FieldText[15];
            for (int i = 0; i < fields.Length; i++)
                fields[i] = new FieldText("Factor " + (i + 1).ToString() + " values", "List of values seperated by a semicolon", "");
            new InputBox(_excelapp, "Generate exhaustive combinations", fields).Show(delegate {
                List<string[]> valuesList = new List<string[]>();
                for (int i = fields.Length - 1; i >= 0; i--) {
                    if (string.IsNullOrEmpty(fields[i].Value)) continue;
                    string[] fieldValues = fields[i].Value.Split(';');
                    if (fieldValues.Length != 0)
                        valuesList.Add(fieldValues);
                }
                if (valuesList.Count == 0)
                    return;
                var data = Combinaison.Get(valuesList);
                CellsOperator.AddDataToRange(_range, data);
            });
        }

        public void Taguchi_L4_3_2() {
            GenExperiment(Experiment.Taguchi_L4_3_2);
        }

        public void Taguchi_L8_7_2() {
            GenExperiment(Experiment.Taguchi_L8_7_2);
        }

        public void Taguchi_L12_11_2() {
            GenExperiment(Experiment.Taguchi_L12_11_2);
        }

        public void Taguchi_L16_15_2() {
            GenExperiment(Experiment.Taguchi_L16_15_2);
        }

        public void Taguchi_L9_4_3() {
            GenExperiment(Experiment.Taguchi_L9_4_3);
        }

        public void Taguchi_L27_13_3() {
            GenExperiment(Experiment.Taguchi_L27_13_3);
        }

        public void Taguchi_L36_13_3() {
            GenExperiment(Experiment.Taguchi_L36_13_3);
        }

        public void Taguchi_L16_5_4() {
            GenExperiment(Experiment.Taguchi_L16_5_4);
        }

        public void Taguchi_L25_6_5() {
            GenExperiment(Experiment.Taguchi_L25_6_5);
        }

        public void Taguchi_L8_4_2_1_4() {
            GenExperiment(Experiment.Taguchi_L8_4_2_1_4);
        }

        public void Taguchi_L16_12_2_1_4() {
            GenExperiment(Experiment.Taguchi_L16_12_2_1_4);
        }

        public void Taguchi_L16_9_2_2_4() {
            GenExperiment(Experiment.Taguchi_L16_9_2_2_4);
        }

        public void Taguchi_L16_6_2_3_4() {
            GenExperiment(Experiment.Taguchi_L16_6_2_3_4);
        }

        public void Taguchi_L16_3_2_4_4() {
            GenExperiment(Experiment.Taguchi_L16_3_2_4_4);
        }

        public void Taguchi_L18_1_2_7_3() {
            GenExperiment(Experiment.Taguchi_L18_1_2_7_3);
        }

        public void Orthogonal_4_3_2_2() {
            GenExperiment(Experiment.Orthogonal_4_3_2_2);
        }

        public void Orthogonal_8_5_2_2() {
            GenExperiment(Experiment.Orthogonal_8_5_2_2);
        }

        public void Orthogonal_8_7_2_2() {
            GenExperiment(Experiment.Orthogonal_8_7_2_2);
        }

        public void Orthogonal_12_11_2_2() {
            GenExperiment(Experiment.Orthogonal_12_11_2_2);
        }

        public void Orthogonal_16_15_2_2() {
            GenExperiment(Experiment.Orthogonal_16_15_2_2);
        }

        public void Orthogonal_20_19_2_2() {
            GenExperiment(Experiment.Orthogonal_20_19_2_2);
        }

        public void Orthogonal_8_4_2_3() {
            GenExperiment(Experiment.Orthogonal_8_4_2_3);
        }

        public void Orthogonal_16_8_2_3() {
            GenExperiment(Experiment.Orthogonal_16_8_2_3);
        }

        public void Orthogonal_24_12_2_3() {
            GenExperiment(Experiment.Orthogonal_24_12_2_3);
        }

        public void Orthogonal_32_16_2_3() {
            GenExperiment(Experiment.Orthogonal_32_16_2_3);
        }

        public void Orthogonal_40_20_2_3() {
            GenExperiment(Experiment.Orthogonal_40_20_2_3);
        }

        public void Orthogonal_9_4_3_2() {
            GenExperiment(Experiment.Orthogonal_9_4_3_2);
        }

        public void Orthogonal_18_7_3_2() {
            GenExperiment(Experiment.Orthogonal_18_7_3_2);
        }

        public void Orthogonal_27_13_3_2() {
            GenExperiment(Experiment.Orthogonal_27_13_3_2);
        }

        public void Orthogonal_16_5_4_2() {
            GenExperiment(Experiment.Orthogonal_16_5_4_2);
        }

        public void Orthogonal_32_9_4_2() {
            GenExperiment(Experiment.Orthogonal_32_9_4_2);
        }

        public void Orthogonal_25_6_5_2() {
            GenExperiment(Experiment.Orthogonal_25_6_5_2);
        }

        private void GenExperiment(Experiment experiment) {
            var fields = new FieldText[experiment.NbFactor];
            for (int i = 0; i < experiment.NbFactor; i++) {
                fields[i] = new FieldText("F" + (i + 1).ToString() + " values (" + experiment.NbLevels[i] + ")", "List of values seperated by a semicolon", "");
                fields[i].MaxValues = experiment.NbLevels[i];
            }
            new InputBox(_excelapp, "Generate experiment", fields).Show(delegate {
                var valuesList = new List<string[]>(experiment.NbFactor);
                for (int i = 0; i < fields.Length; i++) {
                    if (string.IsNullOrEmpty(fields[i].Value))
                        valuesList.Add(null);
                    else {
                        valuesList.Add(fields[i].Value.Split(';'));
                    }
                }
                CellsOperator.AddDataToRange(_range, experiment.GetArray(valuesList));
            });
        }

    }
}
